Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client error console and duplicate input/output binding errors #3931

Merged
merged 36 commits into from
Nov 27, 2023

Conversation

nstrayer
Copy link
Contributor

What?

This PR adds a client-side error console for surfacing errors to the user that previously would go unseen unless they had their browser's devtools open.

Also included is special detection of duplicate input and output bindings which would previously silently either break your app (in the case of outputs) or cause confusing behavior (only first bound input would work.)

image

There is support for multiple errors:
image

The following test app will trigger input and output binding errors. (Note you'll need to comment out the second output to get the input error to show up because output binding errors stop the entire Shiny runtime.

library(shiny)

ui <- fluidPage(

    # Application title
    titlePanel("Old Faithful Geyser Data"),

    # Sidebar with a slider input for number of bins
    sidebarLayout(
        sidebarPanel(
            sliderInput("bins",
                        "Number of bins:",
                        min = 1,
                        max = 50,
                        value = 30),
            numericInput("bins","Number of bins:",value = 30)
        ),
        mainPanel(
           # Multiple binding collisions will be caught and displayed in a
           # single error message
           plotOutput("distPlot", width = "50%"),
           plotOutput("distPlot"),
           plotOutput("secondPlot", width = "50%"),
           plotOutput("secondPlot")
        )
    )
)

server <- function(input, output) {
    output$distPlot <- renderPlot({
        # generate bins based on input$bins from ui.R
        x    <- faithful[, 2]
        bins <- seq(min(x), max(x), length.out = input$bins + 1)

        # draw the histogram with the specified number of bins
        hist(x, breaks = bins, col = 'darkgray', border = 'white',
             xlab = 'Waiting time to next eruption (in mins)',
             main = 'Histogram of waiting times')
    })
}

# Run the application
shinyApp(ui = ui, server = server)

How?

Adds two webcomponents: shiny-error-console-container and shiny-error-console. A function showErrorInClientConsole(e) adds the errors to the page (as children of a shiny-error-console-container for layout purposes) as they come in.

@nstrayer nstrayer marked this pull request as draft October 30, 2023 16:01
@nstrayer nstrayer requested a review from wch October 30, 2023 17:13
@nstrayer
Copy link
Contributor Author

App that shows catching of duplicate IDs across static and dynamic UI

library(shiny)

ui <- fluidPage(
    sidebarLayout(
        sidebarPanel(
            sliderInput("bins",
                        "Number of bins:",
                        min = 1,
                        max = 50,
                        value = 30),
            checkboxInput("showDynamicPlot", "Show dynamic plot")
        ),
        mainPanel(
           plotOutput("distPlot"),
           # Uncomment this line to trigger initialization duplicate error
           # plotOutput("distPlot", width = "50%"),
           uiOutput("dynamicPlot"),
        )
    )
)

server <- function(input, output) {

    output$distPlot <- renderPlot({
        x    <- faithful[, 2]
        bins <- seq(min(x), max(x), length.out = input$bins + 1)

        hist(x, breaks = bins, col = 'darkgray', border = 'white',
             xlab = 'Waiting time to next eruption (in mins)',
             main = 'Histogram of waiting times')
    })

    output$dynamicPlot <- renderUI({
      if (input$showDynamicPlot) {
        sliderInput("bins",
          "Number of bins:",
          min = 1,
          max = 50,
          value = 30)
      }
    })
}

shinyApp(ui = ui, server = server)

@nstrayer nstrayer marked this pull request as ready for review October 30, 2023 18:50
…n it. Also add a close button and a collapse one
@nstrayer
Copy link
Contributor Author

nstrayer commented Nov 2, 2023

image The latest commit changes the styles so that the container component holds most of the logic. Also adds a dismiss button and a collapse button.

@michaelhogersnplm
Copy link

Hello @nstrayer, great addition - it certainly has the potential to reduce confusion occasionally. I'm curious, though: could there be a feature to switch this off, perhaps through a setting, for instances where it might be preferable to keep it concealed in a production environment?

Best,
Michael

@CLAassistant
Copy link

CLAassistant commented Nov 8, 2023

CLA assistant check
All committers have signed the CLA.

srcts/src/shiny/bind.ts Outdated Show resolved Hide resolved
srcts/src/shiny/bind.ts Outdated Show resolved Hide resolved
srcts/src/shiny/bind.ts Outdated Show resolved Hide resolved
@nstrayer
Copy link
Contributor Author

Hello @nstrayer, great addition - it certainly has the potential to reduce confusion occasionally. I'm curious, though: could there be a feature to switch this off, perhaps through a setting, for instances where it might be preferable to keep it concealed in a production environment?

Best, Michael

Hi Michael,

This is a good point. Right now this only should catch errors that would have otherwise broken your app. We're going to try bringing it in where it is always active and then run our large suite of tests on Shiny to see if we're going to start causing spurious error messages to appear. If it becomes a problem then the plan would be to make it only show up when devmode is turned on for Shiny.

…between inputs and outputs"

This reverts commit 0cff500.
…ve, and validate. The validation is now done after _bindAll() is called to get a complete picture of collisions.
@nstrayer
Copy link
Contributor Author

In 34cf27e, I switched where the logic for throwing on duplicate ID bindings was. Now we let Shiny run through all the input and output bindings. Then after both bindInputs() and bindOtuputs() have run, we validate and throw a much more helpful error message that contains all the duplicates. This means we can get error messages like this:

The following IDs were repeated: 
"vars": (2 inputs and 1 output), 
"bins": (2 inputs)

@nstrayer
Copy link
Contributor Author

This is what the current error messages look like, since the previous screenshots are out-of-date:
image

Comment on lines 207 to 208
// Check if ID is falsy, or if already bound, or has the same ID as
// another input binding and if it is, skip
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix comment

* duplicate IDs but in the future could be expanded to check more conditions
* @returns ShinyClientError if current ID bindings are invalid, otherwise null
*/
function getValidity():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to checkValidity.


if (duplicateIds.size === 0) return { status: "ok" };

const duplicateIdMsg = [...duplicateIds.entries()]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a more efficient copying technique.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants